#pragma once
//从服务器那里获取对应的消息进行转发！
#include <unordered_set>
#include "InetAddr.hpp"
#include "Log.hpp"


using namespace myLog;
using namespace myMutex;

//注意，传递给哈希表的仿函数应该是const成员函数！
struct InetAddrHash{
    size_t operator() (const InetAddr& in) const {
        std::string ret = std::to_string(in.GetPort()) + in.GetIp();

        size_t hash = 0;
        for (auto ch : ret) {
	        hash += ch;
	        hash *= 131;
        }
        return hash;
    }
};

struct InetAddr_Equal{
    bool operator() (const InetAddr& in1, const InetAddr& in2) const{
        return in1 == in2;
    }
};


class Route{
private:
//这里势必会使用到自定义类InetAddr的判断相等逻辑，但是，这个需要我们自行在内部进行重载！
    bool IsExist(const InetAddr& send_client){
        auto rit = _online_clients.find(send_client);
        return rit != _online_clients.end();
    }

    bool AddClient(const InetAddr& send_client){
        if(IsExist(send_client)){
            LOG(LogLevel::WARNING) << "客户端 : " << send_client.GetFormatStr() << "已经存在";
            return false;
        }
        else{
            auto rit = _online_clients.insert(send_client);
            return rit.second;
        }
    }

    bool DeleteClient(const InetAddr& send_client){
        if(!IsExist(send_client)){
            LOG(LogLevel::WARNING) << "客户端 : " << send_client.GetFormatStr() << "不存在";
            return false;
        }
        else{
            size_t ret = _online_clients.erase(send_client);
            return ret;
        }
    }

public:
    Route(){}

    ~Route(){}

//负责把send_clinet客户端发送的信息，接收后，进行转发  -> 群发！
    void MessageRoute(const std::string& message, const InetAddr& send_client){
        //直接简单粗暴上一把锁 -> 后序会根据协议来操作
        Lock_Guard guard(__online_clients_lock);

        if(!IsExist(send_client)){
            bool IsAdd = AddClient(send_client);
            if(IsAdd) LOG(LogLevel::DEBUG) << "新增客户端 : " << send_client.GetFormatStr();    
            else LOG(LogLevel::WARNING) << "客户端 : " << send_client.GetFormatStr() << "添加失败";
        }

        //无论接收到什么信息 -> 都要转发给所有的在线用户！
        //因为没有自定义协议，所以，简单的规定一下发送EXIT就是退出群聊！
        //转发信息前，我们需要对转发的信息做一下基本处理：添加谁发的(ip + port)？ 基本格式
        std::string forward_msg = send_client.GetFormatStr() + "# " + message;

        //然后把forward_msg发送给所有的在线客户端(_online_clients内的)
        //但是，有一个问题，就是发送信息需要socket的描述符！这个从哪里来？
        //Route这个类是服务器的进程在使用，服务器创建起来后，就有一个文件描述符了！ 
        //-> 所以，服务器传递这个类进来给任务用的时候，要传进一个InetAddr类，把描述符给进这个类里面即可
        for(auto &user : _online_clients){
            ssize_t n = sendto(user.GetSocket_fd(), forward_msg.c_str(), forward_msg.size(), 
            0, (struct sockaddr*)&(user.GetSockAddr_in()), sizeof(user.GetSockAddr_in()));
            if(n < 0) {LOG(LogLevel::WARNING) << "向客户端 : " << user.GetFormatStr() << "转发" << message << " 失败";}
            else {LOG(LogLevel::WARNING) << "向客户端 : " << user.GetFormatStr() << "转发" << message << " 成功"; }
        }

        if(message == "EXIT"){
            LOG(LogLevel::INFO) << "客户端: " << send_client.GetFormatStr() << "需要EXIT";
            bool flag = DeleteClient(send_client);
            if(flag) LOG(LogLevel::INFO) << "客户端" << send_client.GetFormatStr() << "EXIT SUCCESS";
            else LOG(LogLevel::ERROR) << "客户端" << send_client.GetFormatStr() << "EXIT FAILED";
        }
    }

    
private:
//因为要群发消息，所以需要管理 
    std::unordered_set<InetAddr, InetAddrHash, InetAddr_Equal> _online_clients;
//管理在线用户 -> 只要第一次发送信息，就默认添加到内部
    //这里需要注意，因为哈希表里面存储的是我们自己的类，是需要提供一下哈希函数的，即想办法把类转化为一个整数
    //而且，还有一个问题就是，自定义类需要自己提供比较函数！


    //这里需要说一个问题就是：
    //虽然线程池中获取任务的时候已经互斥了，但是每个线程都要执行路由转发，每个线程都在访问 _online_clients
    //很明显，这是一个共享资源

    //实际上，进行路由转发的时候，信息不可能是单纯的信息，是需要有协议的！(比如私发? 群发? 退出?...)
    //但是今天这里并没有什么协议，所以直接就上一把锁就完事了

    Mutex __online_clients_lock;
};